
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>


#define MEMDEV_NR_DEVS          3

#define MEMDEV_IOC_MAGIC        'M'
#define MEMDEV_IOC_CLEAR        _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOC_FILL         _IOW(MEMDEV_IOC_MAGIC, 2, char)
#define MEMDEV_IOC_MAXNR        2

struct mem_device {
    struct miscdevice msdev;
    struct device *dev;

    void *data;
    size_t size;
};

static inline struct mem_device *to_mdev(struct file *filp)
{
    struct miscdevice *dev = filp->private_data;
    return container_of(dev, struct mem_device, msdev);
}

static ssize_t mem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    struct mem_device *dev = to_mdev(filp);

    if (*ppos >= dev->size) {
        return 0;
    }
    if (count > dev->size) {
        count = dev->size;
    }
    ret = copy_to_user(buf, dev->data + *ppos, count);
    if (ret) {
        ret = -EFAULT;
    } else {
        *ppos += count;
        ret = count;
    }

    return ret;
}

static ssize_t mem_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    struct mem_device *dev = to_mdev(filp);

    if (*ppos >= dev->size) {
        return 0;
    }

    if (count + *ppos > dev->size) {
        count = dev->size - *ppos;
    }

    ret = copy_from_user(dev->data + *ppos, buf, count);
    if (ret) {
        ret = -EFAULT;
    } else {
        *ppos += count;
        memset(dev->data + *ppos, 0, dev->size - *ppos);
        ret = count;
    }

    return ret;
}

static long mem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    struct mem_device *dev = to_mdev(filp);
    if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) {
        return -ENOTTY;
    }
    if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) {
        return -ENOTTY;
    }

    switch (cmd) {
    case MEMDEV_IOC_CLEAR:
        memset(dev->data, 0x00, dev->size);
        filp->f_pos = 0;
        break;

    case MEMDEV_IOC_FILL:
        memset(dev->data, (char)arg, dev->size);
        filp->f_pos = dev->size;
        break;

    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

static const struct file_operations mem_fops = {
    .owner      = THIS_MODULE,
    .read       = mem_read,
    .write      = mem_write,
    .llseek     = default_llseek,
    .unlocked_ioctl = mem_ioctl,
    .compat_ioctl   = compat_ptr_ioctl,
};

static struct mem_device *mdev_init(struct platform_device *pdev)
{
    int ret;
    struct mem_device *mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
    if (mdev) {
        mdev->msdev.minor = MISC_DYNAMIC_MINOR;
        mdev->msdev.fops = &mem_fops;
        mdev->msdev.name = kasprintf(GFP_KERNEL, "%s%d", KBUILD_MODNAME, pdev->id);
        if (!mdev->msdev.name) {
            goto fail;
        }

        ret = misc_register(&mdev->msdev);
        if (ret < 0) {
            pr_err("device_register failed\n");
            goto fail1;
        }

        mdev->size = 4 * 1024;
        mdev->data = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, get_order(mdev->size));
        // mdev->data = kzalloc(mdev->size, GFP_KERNEL);
        pr_debug("mdev->data: %px\n", mdev->data);
        if (IS_ERR_OR_NULL(mdev->data)) {
            pr_err("memory alloc failed\n");
            goto fail2;
        }
    }

    return mdev;
fail2:
    misc_deregister(&mdev->msdev);
fail1:
    kfree(mdev->msdev.name);
fail:
    kfree(mdev);
    return NULL;
}

static void mdev_deinit(struct mem_device *mdev)
{
    if (mdev->data) {
        free_pages((unsigned long)mdev->data, get_order(mdev->size));
        // kfree(mdev->data);
    }
    misc_deregister(&mdev->msdev);
    kfree(mdev->msdev.name);
    kfree(mdev);
}

static int memdev_probe(struct platform_device *pdev)
{
    int ret;
    struct mem_device *mdev = mdev_init(pdev);  /* pdev->id must be unique */

    if (!mdev) {
        ret = -ENOMEM;
        goto fail;
    }
    mdev->dev = &pdev->dev;
    platform_set_drvdata(pdev, mdev);
fail:
    return ret;
}

static int memdev_remove(struct platform_device *pdev)
{
    struct mem_device *mdev = platform_get_drvdata(pdev);
    mdev_deinit(mdev);
    platform_set_drvdata(pdev, NULL);
    return 0;
}

static struct platform_driver memdev_pdrv = {
    .probe      = memdev_probe,
    .remove     = memdev_remove,
    .driver     = {
        .name   = "mem-device",
    },
};

static struct platform_device *memdev_pdevs[MEMDEV_NR_DEVS];

static int __init memdev_init(void)
{
    int ret;
    int i;
    for (i = 0; i < MEMDEV_NR_DEVS; i++) {
        struct platform_device *pdev = platform_device_alloc("mem-device", i);
        ret = platform_device_register(pdev);
        if (ret) {
            int j;
            for (j = 0; j < i; j++) {
                platform_device_unregister(memdev_pdevs[j]);
            }
            return ret;
        }
        memdev_pdevs[i] = pdev;
    }

    ret = platform_driver_register(&memdev_pdrv);
    if (ret) {
        for (i = 0; i < MEMDEV_NR_DEVS; i++) {
            platform_device_unregister(memdev_pdevs[i]);
        }
    }
    return ret;
}

static void __exit memdev_exit(void)
{
    int i;
    platform_driver_unregister(&memdev_pdrv);
    for (i = 0; i < MEMDEV_NR_DEVS; i++) {
        platform_device_unregister(memdev_pdevs[i]);
    }
}

module_init(memdev_init);
module_exit(memdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("kingdix10");
